package server;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.rmi.RemoteException;
import common.GameState;
import common.Position;
import common.ClientCallback;

public class MazeData {
	
	final static int MAX_PLAYER_NUM = 128;
	final static int WAITING_PERIOD_4_JOINING = 1000 * 20; 
	
	/**
	 * NxN grid, M treasures
	 */
	private int numberOfPlayers, dimension, nTreasures;
	private Position[] location;
	private int[] collected;
	private int[][] grid;
	private ClientCallback[] clientCallbacks;
	private long[] last_move_time;
	private boolean started;	
	private Timer timer;
	private HashSet<Integer> crashedClients;

	
	
	public MazeData(int N, int M) {
		this.dimension = N;
		this.nTreasures = M;
		started = false;
		numberOfPlayers = 0;
		clientCallbacks = new ClientCallback[MAX_PLAYER_NUM];
		last_move_time = new long[MAX_PLAYER_NUM];
		crashedClients = new HashSet<Integer>();
		grid = new int[N][N];
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				grid[i][j] = 0;
			}
		}
		// randomize the treasure locations
		Random random = new Random();
		for (int i = 0; i < M; i++) {
			int random_int = random.nextInt(N * N);
			int r = random_int / N;
			int c = random_int % N;
			grid[r][c]++;
		}
	}
	public void printState()
	{
		//print column header
		System.out.print("\t");			
		for(int c =0; c< this.dimension; ++c)
		{
			System.out.print("c" + c + "\t");			
		}
		System.out.println();
		//print row header and grid
		for(int r =0; r < this.dimension; ++r)
		{
			
			for(int c =0; c< this.dimension; ++c)
			{
				if(c== 0)
				{
					System.out.print("r" + r + "\t");
				}
				System.out.print(grid[r][c]);
				System.out.print("\t");
			}
			System.out.println();
		}
		if(this.numberOfPlayers > 0)
		{
			
		}
	}
	public boolean isStarted() {
		return started;
	}
	protected void startGame()
	{
		started = true;
		this.initPlayerData();
		this.callBackPlayer();
	}
 	public synchronized void initPlayerData() {
		location = new Position[numberOfPlayers];
		collected = new int[numberOfPlayers];
		
		// randomize players' locations
		Random random = new Random();
		for (int i = 0; i < numberOfPlayers; i++) {
			do {
				int random_int = random.nextInt(dimension * dimension);
				int r = random_int / dimension;
				int c = random_int % dimension;
				if (grid[r][c] == 0 && grid[r][c] != -1) {
					grid[r][c] = -1;
					location[i] = new Position(r, c);
					break;
				}
			} while (true);
		}
		//System.out.println("Players location generated!");
		// undo the -1 flag of grid
		for (int i = 0; i < numberOfPlayers; i++) {
			grid[location[i].x][location[i].y] = 0;
		}
		// set all collected number to 0
		for (int i = 0; i < numberOfPlayers; i++) {
			collected[i] = 0;
		}
	}
	private GameState getCurrentGameState() {
		return new GameState(location, collected, grid, dimension, numberOfPlayers);
	}
	private boolean isInGrid(int x, int y) {
		return x >= 0 && x < dimension && y >= 0 && y < dimension;
	}
	private Position getNewLocation(Position loc, String direction, StringBuilder d) {
		int nx = loc.x, ny = loc.y;
		direction = direction.toUpperCase();
		
		if (direction.equals("W")) { //up
			nx = loc.x - 1;
			ny = loc.y;
			d.append("up");
		} else if (direction.equals("S")) { //down
			nx = loc.x +1;
			ny = loc.y ;
			d.append("down");
		}
		else if (direction.equals("A")) {  //left
			nx = loc.x;
			ny = loc.y - 1;
			d.append("left");
		} else if (direction.equals("D")) { //right
			nx = loc.x ;
			ny = loc.y +1;
			d.append("right");
		
		} else if (direction.equals("NOMOVE")) {
			nx = loc.x;
			ny = loc.y;
			d.append("NOMOVE");			
		} 
		if (isInGrid(nx, ny)) {
			return new Position(nx, ny);
		} else {
			return loc;
		}
	}
	
	
	public synchronized int addPlayer(ClientCallback game_monitor) {
		if (numberOfPlayers >= MAX_PLAYER_NUM) {
			return -1;
		}
		//start the game when the first person join the game
		if(numberOfPlayers == 0) 
		{
			 timer = new Timer();
			 timer.schedule(new TimeCountingTask(this), WAITING_PERIOD_4_JOINING);
			 timer.schedule(new HeartBeatTask(this), 1000, 5000);
		}
		clientCallbacks[numberOfPlayers++] = game_monitor;
		System.out.println("New player joined, ID: " + (numberOfPlayers - 1));
		return numberOfPlayers -1 ;
	}

	public synchronized GameState move(int id, String direction) {
		StringBuilder  d = new StringBuilder(""); 
		Position new_loc = getNewLocation(location[id], direction,d);
		location[id] = new_loc;
		int nCollected =grid[new_loc.x][new_loc.y];
		if (grid[new_loc.x][new_loc.y] > 0) {
			collected[id] += grid[new_loc.x][new_loc.y];
			grid[new_loc.x][new_loc.y] = 0;
		}
		
		//not heartbeat calls
		if(!"NOMOVE".equals(d.toString()))
		{
/*			System.out.println("Player " + id + " moves " 
					+ d  + " to loc [" + new_loc.x + ", " + new_loc.y + "] and collected " 
					+ nCollected + " treasures newly");*/
			last_move_time[id] = System.currentTimeMillis();
		}
		
		GameState ret = getCurrentGameState();
		return ret;
	}
	public synchronized void checkPlayers() {
		//System.out.println("Checking if any player crashes...");
		for (int i = 0; i < numberOfPlayers; i++) {	// num_of_player may change here due to player crash
			try {
				if(!crashedClients.contains(i))
				{
					clientCallbacks[i].isPlayerCrash();
				}
			} catch (RemoteException e) {
				System.out.println("Player " + i + " Crashed!");
				crashedClients.add(i);
			}
		}
		//System.out.println("Finishes checking!");
	}
	public synchronized void callBackPlayer() {
		//System.out.println("Start to call back players...");
		GameState state = getCurrentGameState();
		for (int i = 0; i < numberOfPlayers; i++) {	// num_of_player may change here due to player crash
			try {
				clientCallbacks[i].startGame(state);
				//System.out.println("Call back player " + i);
			} catch (RemoteException e) {
				System.out.println("Player " + i + " Exception: Crash!");
				//deletePlayer(i);
			}
		}
	}
	private void deletePlayer(int id) {
		for (int i = id; i < numberOfPlayers - 1; i++) {
			location[i] = location[i+1];
			collected[i] = collected[i+1];
			clientCallbacks[i] = clientCallbacks[i+1];
			last_move_time[i] = last_move_time[i+1];
		}
		numberOfPlayers--;
	}
	
	class TimeCountingTask extends TimerTask
	{
		
		private MazeData parent; 
		public TimeCountingTask(MazeData parent)
		{
			this.parent = parent;
		}
		 public void run() {
		      //System.out.println("Counting down time's up for game starting!");
		      this.parent.startGame();
		      //timer.cancel(); //Not necessary because we call System.exit
		      //System.exit(0); //Stops the AWT thread (and everything else)
		    }
	}
	class HeartBeatTask extends TimerTask
	{
		private MazeData parent; 
		public HeartBeatTask(MazeData parent)
		{
			this.parent = parent;
		}
		@Override
		public void run() {			
			this.parent.checkPlayers();			
		}
		
	}
}
